﻿//-----------------------------------------------------------------------
// <copyright file="JSONClipboard.cs" company="Nathan Miller">
// Copyright (c) Nathan Miller.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </copyright>
//-----------------------------------------------------------------------
namespace TileTool.Base.Clipboard
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;
    using Newtonsoft.Json;
    using TileTool.Clipboard;
    using TileTool.DataBag;
    using TileTool.Log;
    using TileTool.PerfSpy;

    /// <summary>
    /// Implementation of the IClipboard interface which stores data in the clipboard
    /// using JSON.
    /// </summary>
    public class JSONClipboard : IClipboard
    {
        #region Constants

        /// <summary>
        /// Log group to use.
        /// </summary>
        private const string LogGroup = "JSONClipboard";

        #endregion

        #region Methods - Public

        /// <summary>
        /// Gets clipboard data from the clipboard.
        /// 
        /// Will return null if there is no data or the clipboard data is invalid.
        /// </summary>
        /// <returns>Returns the clipboard data or null if there was no data or it was invalid.</returns>
        public ClipboardData GetData()
        {
            string clipboardText = System.Windows.Clipboard.GetText();
            if (string.IsNullOrEmpty(clipboardText))
            {
                return null;
            }

            try
            {
                using (new TimedBlock(LogGroup + ".GetData()"))
                {
                    using (StringReader stringReader = new StringReader(clipboardText))
                    {
                        using (JsonTextReader jsonReader = new JsonTextReader(stringReader))
                        {
                            JsonSerializerSettings jsonSettings = this.AllocateSettings();
                            JsonSerializer serializer = JsonSerializer.Create(jsonSettings);
                            IDictionary<string, IDictionary<string, string[]>> clipDictionary =
                                serializer.Deserialize<IDictionary<string, IDictionary<string, string[]>>>(jsonReader);

                            return this.BuildClipboardData(clipDictionary);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                LogSystem.LogError(LogGroup, "Failed to get clipboard data: " + e.Message);
                LogSystem.LogError(LogGroup, "Exception: " + e.ToString());
            }

            return null;
        }

        /// <summary>
        /// Put the passed data into the clipboard.
        /// </summary>
        /// <param name="data">Data to put in the clipboard.</param>
        public void SetData(ClipboardData data)
        {
            if (data == null)
            {
                throw new ArgumentNullException("data", "Clipboard data must be non-null.");
            }

            IDictionary<string, IDictionary<string, string[]>> clipboardDict = this.BuildClipboardDictionary(data);

            try
            {
                using (new TimedBlock(LogGroup + ".SetData()"))
                {
                    StringBuilder stringBuilder = new StringBuilder();

                    using (StringWriter stringWriter = new StringWriter(stringBuilder))
                    {
                        using (JsonTextWriter jsonWriter = new JsonTextWriter(stringWriter))
                        {
                            JsonSerializerSettings jsonSettings = this.AllocateSettings();
                            JsonSerializer serializer = JsonSerializer.Create(jsonSettings);

                            jsonWriter.Formatting = Formatting.Indented;
                            jsonWriter.Indentation = 4;
                            jsonWriter.IndentChar = ' ';

                            serializer.Serialize(jsonWriter, clipboardDict);

                            string jsonString = stringBuilder.ToString();

                            System.Windows.Clipboard.SetText(jsonString);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                LogSystem.LogError(LogGroup, "Failed to set clipboard data: " + e.Message);
                LogSystem.LogError(LogGroup, "Exception: " + e.ToString());
            }
        }

        #endregion

        #region Methods - Private

        /// <summary>
        /// Given a clipboard data dictionary, build clipboard data from it.
        /// </summary>
        /// <param name="clipboardDict">Clipboard data dictionary.</param>
        /// <returns>Returns clipboard data if we were able to build it or null if we were not.</returns>
        private ClipboardData BuildClipboardData(IDictionary<string, IDictionary<string, string[]>> clipboardDict)
        {
            bool valid = true;
            ClipboardData resData = null;

            foreach (KeyValuePair<string, IDictionary<string, string[]>> item in clipboardDict)
            {
                string key = item.Key;
                if (string.IsNullOrEmpty(key))
                {
                    valid = false;
                    LogSystem.LogError(LogGroup, "BuildClipboardData() - Found null or empty key for data bag values.");
                    break;
                }

                IDictionary<string, string[]> value = item.Value;
                DataBag bag = DataBag.BuildFromStringDictionary(value);
                if (bag == null)
                {
                    valid = false;
                    LogSystem.LogError(LogGroup, "BuildClipboardData() - Failed to build data bag from string dictionary: " + value.ToString());
                    break;
                }

                if (resData == null)
                {
                    resData = new ClipboardData();
                }

                resData.DataDictionary[key] = bag;
            }

            if (!valid)
            {
                return null;
            }

            return resData;
        }

        /// <summary>
        /// Build the dictionary that we will store in the clipboard.
        /// </summary>
        /// <param name="data">Clipboard data.</param>
        /// <returns>Returns the clipboard dictionary.</returns>
        private IDictionary<string, IDictionary<string, string[]>> BuildClipboardDictionary(ClipboardData data)
        {
            IDictionary<string, IDataBag> dict = data.DataDictionary;
            IDictionary<string, IDictionary<string, string[]>> clipboardDict = new Dictionary<string, IDictionary<string, string[]>>();

            foreach (KeyValuePair<string, IDataBag> item in dict)
            {
                string name = item.Key;
                IDataBag value = item.Value;
                if (value == null)
                {
                    continue;
                }

                IDictionary<string, string[]> bagDict = value.ToStringDictionary();

                clipboardDict.Add(name, bagDict);
            }

            return clipboardDict;
        }

        /// <summary>
        /// Allocate a JSON settings object.
        /// </summary>
        /// <returns>Returns the JSON settings object.</returns>
        private JsonSerializerSettings AllocateSettings()
        {
            JsonSerializerSettings jsonSettings = new JsonSerializerSettings();

            jsonSettings.NullValueHandling = NullValueHandling.Ignore;
            jsonSettings.ObjectCreationHandling = ObjectCreationHandling.Auto;
            jsonSettings.TypeNameHandling = TypeNameHandling.Auto;

            return jsonSettings;
        }

        #endregion
    }
}
